/* 
Scenic Graphics Library
Copyright (C) 2007 Jouni Tulkki

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USApackage scenic;
*/

package scenic.toolkit;

import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Vector;

import scenic.SceneTransform;
import scenic.Graphics;
import scenic.Util;
import scenic.toolkit.db.NodeListener;
import scenic.toolkit.db.event.ChangeEvent;
import scenic.toolkit.event.MouseListener;
import scenic.toolkit.event.MouseMotionListener;

public class CComponent implements NodeListener {
	private CComponent parent;
	private RootPane rootPane;
	private SceneTransform scene = new SceneTransform(new AffineTransform());
	private double width;
	private double height;
	private boolean isInvalid;
	private Vector<CComponent> children = new Vector<CComponent>();
	private Vector<MouseListener> mouseListeners = new Vector<MouseListener>();
	private Vector<MouseMotionListener> mouseMotionListeners = new Vector<MouseMotionListener>();
	
	public CComponent() {
		invalidate();
	}
	
	public AffineTransform getTotalTransform() {
		if(parent == null)
			return getTransform();
		return Util.multiply(parent.getTotalTransform(), getTransform());
	}
	
	public void addMouseListener(MouseListener listener) {
		mouseListeners.add(listener);
	}
	
	public void removeMouseListener(MouseListener listener) {
		mouseListeners.remove(listener);
	}

	Vector<MouseListener> getMouseListeners() {
		return mouseListeners;
	}
	
	public void addMouseMotionListener(MouseMotionListener listener) {
		mouseMotionListeners.add(listener);
	}
	
	public void removeMouseMotionListener(MouseMotionListener listener) {
		mouseMotionListeners.remove(listener);
	}

	Vector<MouseMotionListener> getMouseMotionListeners() {
		return mouseMotionListeners;
	}
	
	protected void addChild(CComponent child) {
		if(child != null) {
			if(child.parent != null)
				child.parent.removeChild(child);
			child.parent = this;
			children.add(child);
			child.invalidate();
			invalidate();
		}
	}
	
	protected void removeChild(CComponent child) {
		if(child != null)
		{
			child.parent = null;
			children.remove(child);
			invalidate();
		}
	}
	
	protected Vector<CComponent> getChildren() {
		return children;
	}
	
	public void changed(ChangeEvent event) {
		invalidate();
	}
	
	public CComponent getParent() {
		return parent;
	}
	
	public void setParent(CComponent parent) {
		this.parent = parent;
	}
	
	void setRootPane(RootPane rootPane) {
		this.rootPane = rootPane;
	}
	
	RootPane getRootPane() {
		if(parent != null)
			return parent.getRootPane();
		else
			return rootPane;
	}
	
	public SceneTransform getScene() {
		return scene;
	}	
	
	public double getWidth() {
		return width;
	}
	
	public void setWidth(double width) {
		if(this.width != width) {
			this.width = width;
			invalidate();
		}
	}
	
	public double getHeight() {
		return height;
	}
	
	public void setHeight(double height) {
		if(this.height != height) {
			this.height = height;
			invalidate();
		}
	}
	
	public void setSize(double width, double height) {
		if(this.width != width || this.height != height) {
			this.width = width;
			this.height = height;
			invalidate();
		}
	}
	
	public AffineTransform getTransform() {
		return scene.getTransform();
	}
	
	public void setTransform(AffineTransform transform) {
		scene.setTransform(transform);
	}
	
	public void setPosition(Rectangle2D rectangle) {
		setPosition(rectangle.getX(), rectangle.getY(), 
				rectangle.getWidth(), rectangle.getHeight());
	}
	
	public void setPosition(double x, double y, double width, double height) {
		setTransform(AffineTransform.getTranslateInstance(x, y));
		setSize(width, height);
	}
	
	public final Point2D getMinimumSize() {
		return getMinimumSizeFor(Double.MAX_VALUE, Double.MAX_VALUE);
	}
	
	public Point2D getMinimumSizeFor(double width, double height) {
		return new Point2D.Double(this.width, this.height);
	}
	
	public CComponent getChildAt(Point2D p) {
		for(int i = children.size() - 1; i >= 0; i--) {
			if(children.elementAt(i).isInside(p))
				return children.elementAt(i);
		}
		return null;
	}
	
	public boolean isInside(Point2D p) {
		Point2D p2;
		try {
			p2 = getTransform().createInverse().transform(new Point2D.Double(p.getX(), p.getY()), null);
		} catch (NoninvertibleTransformException e) {
			return false;
		}
		
		return p2.getX() >= 0 && p2.getY() >= 0 && p2.getX() < width && p2.getY() < height;
	}
	
	public Point2D alignToPixels(Point2D src, Point2D dst) {
		AffineTransform tm = getTotalTransform();
		Point2D tmp = new Point2D.Double();
		
		if(dst == null)
			dst = new Point2D.Double();
		
		tmp = tm.transform(src, tmp);
		tmp.setLocation(Math.floor(tmp.getX() + 0.5), Math.floor(tmp.getY() + 0.5));
		try {
			dst = tm.inverseTransform(tmp, dst);
		} catch (NoninvertibleTransformException e) {
			return src;
		}
		
		return dst;
	}
	
	public Point2D alignSizeToPixels(Point2D src, Point2D dst) {
		AffineTransform tm = Util.linearTransform(getTotalTransform());
		Point2D tmp = new Point2D.Double();
		
		if(dst == null)
			dst = new Point2D.Double();
		
		tmp = tm.transform(src, tmp);
		tmp.setLocation(Math.floor(tmp.getX() + 0.5), Math.floor(tmp.getY() + 0.5));
		try {
			dst = tm.inverseTransform(tmp, dst);
		} catch (NoninvertibleTransformException e) {
			return src;
		}
		
		return dst;
	}
	
	public void paint(Graphics g) {
	}
	
	public void invalidate() {
		if(!isInvalid) {
			isInvalid = true;
		
			RootPane root = getRootPane();
		
			if(root != null)
				root.invalidateScene();
		}
	}
	
	public void validate() {
		if(isInvalid) {
			paint(new Graphics(scene));
			isInvalid = false;
		}
		for(CComponent c : children)
			c.validate();
	}
}
